import socket
import threading
import sys
import os
import struct

"""
struct——将字节解释为被打包的二进制数据
	此模块在Python 值和表示为 Python 字节对象的 C 结构之间进行转换。
	一些struct函数（struct方法）使用缓冲区参数
"""


def deal_data(conn, addr):
	print(f"接收到来自 {addr} 的连接")
	while True:
		fileinfo_size = struct.calcsize("128sq")  # linux 和 windows 互传 128sl 改为 128sq  机器位数不一样，一个32位一个64位
		"""
		struct.calcsize('128sq') 函数的作用是计算给定格式化字符串的大小（字节数）。
		它返回一个整数值，表示指定格式化字符串所需的字节大小。
		
		在这个特定的例子中，给定的格式化字符串是 '128sq'。它包含了一个长度为 128 的字符串和一个长整型。
		calcsize 函数将计算出这个格式化字符串所需的字节数，并返回结果。
		
		这个函数通常用于在打包和解包二进制数据时，事先确定需要的缓冲区大小。
		通过计算格式化字符串的大小，我们可以预先分配足够大小的缓冲区来存储打包或解包后的数据，避免缓冲区溢出或内存浪费的问题。
		
		以这段代码为例，calcsize('128sq') 的返回值将会是 (128 + sizeof(long))，即长度为 128 的字符串加上一个长整型的大小，以字节数表示。
		"""
		buf = conn.recv(fileinfo_size)
		print("收到的字节流：", buf, type(buf))
		if buf:  # 如果不加这个if，第一个文件传输完成后会自动走到下一句
			# 解压缩了两部分内容，第一部分是128字节的文件名称，第二部分是文件本身
			filename, filesize = struct.unpack("128sq", buf)  # 解压缩数据
			fn = filename.strip(str.encode("\x00"))  # 去除转换来的
			new_filename = os.path.join(str.encode('../'), str.encode('new_') + fn)  # 拼接路径用于写入位置
			print(f'文件新名称为 {new_filename}, 文件大小是 {round(filesize / 1024, 2)}KB')
			
			recvd_size = 0  # 定义已接收文件的大小
			with open(new_filename, "wb") as f:
				print("开始接收...")
				while not recvd_size == filesize:
					if filesize - recvd_size > 1024:
						data = conn.recv(1024)
						recvd_size += len(data)
					else:
						data = conn.recv(filesize - recvd_size)
						recvd_size = filesize
					
					f.write(data)
				print("结束接收...")
		conn.close()
		break


def socket_service():
	try:
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		# 修改 ip，此处ip 必须为服务器端的ip，Linux 做服务端输入 ifconfig 得到 ip
		s.bind(("192.168.111.52", 5555))
		s.listen(10)
	except socket.error as msg:
		print("报错信息：", msg)
		sys.exit(1)
	while True:
		conn, addr = s.accept()
		t = threading.Thread(target=deal_data, args=(conn, addr))
		t.start()


if __name__ == '__main__':
	socket_service()
